// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/cert_net/cert_net_fetcher_impl.h"

#include <string>
#include <utility>

#include "base/compiler_specific.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "net/cert/ct_policy_enforcer.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/multi_log_ct_verifier.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_server_properties_impl.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"

// TODO(eroman): Test that cookies aren't sent.

using base::ASCIIToUTF16;

namespace net {

namespace {

    const base::FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("net/data/cert_net_fetcher_impl_unittest");

    // A non-mock URLRequestContext which can access http:// urls.
    class RequestContext : public URLRequestContext {
    public:
        RequestContext()
            : storage_(this)
        {
            ProxyConfig no_proxy;
            storage_.set_host_resolver(
                std::unique_ptr<HostResolver>(new MockHostResolver));
            storage_.set_cert_verifier(base::WrapUnique(new MockCertVerifier));
            storage_.set_transport_security_state(
                base::WrapUnique(new TransportSecurityState));
            storage_.set_cert_transparency_verifier(
                base::WrapUnique(new MultiLogCTVerifier));
            storage_.set_ct_policy_enforcer(base::WrapUnique(new CTPolicyEnforcer));
            storage_.set_proxy_service(ProxyService::CreateFixed(no_proxy));
            storage_.set_ssl_config_service(new SSLConfigServiceDefaults);
            storage_.set_http_server_properties(
                std::unique_ptr<HttpServerProperties>(new HttpServerPropertiesImpl()));

            HttpNetworkSession::Params params;
            params.host_resolver = host_resolver();
            params.cert_verifier = cert_verifier();
            params.transport_security_state = transport_security_state();
            params.cert_transparency_verifier = cert_transparency_verifier();
            params.ct_policy_enforcer = ct_policy_enforcer();
            params.proxy_service = proxy_service();
            params.ssl_config_service = ssl_config_service();
            params.http_server_properties = http_server_properties();
            storage_.set_http_network_session(
                base::WrapUnique(new HttpNetworkSession(params)));
            storage_.set_http_transaction_factory(base::WrapUnique(new HttpCache(
                storage_.http_network_session(), HttpCache::DefaultBackend::InMemory(0),
                false /* set_up_quic_server_info */)));
            storage_.set_job_factory(base::WrapUnique(new URLRequestJobFactoryImpl()));
        }

        ~RequestContext() override { AssertNoURLRequests(); }

    private:
        URLRequestContextStorage storage_;
    };

    class FetchResult {
    public:
        FetchResult(Error net_error, const std::vector<uint8_t>& response_body)
            : net_error_(net_error)
            , response_body_(response_body)
        {
        }

        void VerifySuccess(const std::string& expected_body)
        {
            EXPECT_EQ(OK, net_error_);
            EXPECT_EQ(expected_body,
                std::string(response_body_.begin(), response_body_.end()));
        }

        void VerifyFailure(Error expected_error)
        {
            EXPECT_EQ(expected_error, net_error_);
            EXPECT_EQ(0u, response_body_.size());
        }

    private:
        const Error net_error_;
        const std::vector<uint8_t> response_body_;
    };

    // Helper to synchronously wait for the fetch completion. This is similar to
    // net's TestCompletionCallback, but built around FetchCallback.
    class TestFetchCallback {
    public:
        TestFetchCallback()
            : callback_(base::Bind(&TestFetchCallback::OnCallback,
                base::Unretained(this)))
        {
        }

        const CertNetFetcher::FetchCallback& callback() const { return callback_; }

        std::unique_ptr<FetchResult> WaitForResult()
        {
            DCHECK(quit_closure_.is_null());
            while (!HasResult()) {
                base::RunLoop run_loop;
                quit_closure_ = run_loop.QuitClosure();
                run_loop.Run();
                quit_closure_.Reset();
            }
            return std::move(result_);
        }

        bool HasResult() const { return result_.get(); }

        // Sets an extra action (in addition to recording the result) that is run when
        // the FetchCallback is invoked.
        void set_extra_closure(const base::Closure& closure)
        {
            extra_closure_ = closure;
        }

    private:
        void OnCallback(Error net_error, const std::vector<uint8_t>& response_body)
        {
            DCHECK(!HasResult());
            result_.reset(new FetchResult(net_error, response_body));

            if (!extra_closure_.is_null())
                extra_closure_.Run();

            if (!quit_closure_.is_null())
                quit_closure_.Run();
        }

        CertNetFetcher::FetchCallback callback_;
        std::unique_ptr<FetchResult> result_;
        base::Closure quit_closure_;
        base::Closure extra_closure_;
    };

} // namespace

class CertNetFetcherImplTest : public PlatformTest {
public:
    CertNetFetcherImplTest()
    {
        test_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
        context_.set_network_delegate(&network_delegate_);
    }

protected:
    EmbeddedTestServer test_server_;
    TestNetworkDelegate network_delegate_;
    RequestContext context_;
};

// Helper to start an AIA fetch using default parameters.
WARN_UNUSED_RESULT std::unique_ptr<CertNetFetcher::Request> StartRequest(
    CertNetFetcher* fetcher,
    const GURL& url,
    const TestFetchCallback& callback)
{
    return fetcher->FetchCaIssuers(url, CertNetFetcher::DEFAULT,
        CertNetFetcher::DEFAULT, callback.callback());
}

// Fetch a few unique URLs using GET in parallel. Each URL has a different body
// and Content-Type.
TEST_F(CertNetFetcherImplTest, ParallelFetchNoDuplicates)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);
    TestFetchCallback callback1;
    TestFetchCallback callback2;
    TestFetchCallback callback3;

    // Request a URL with Content-Type "application/pkix-cert"
    GURL url1 = test_server_.GetURL("/cert.crt");
    std::unique_ptr<CertNetFetcher::Request> request1 = StartRequest(&fetcher, url1, callback1);

    // Request a URL with Content-Type "application/pkix-crl"
    GURL url2 = test_server_.GetURL("/root.crl");
    std::unique_ptr<CertNetFetcher::Request> request2 = StartRequest(&fetcher, url2, callback2);

    // Request a URL with Content-Type "application/pkcs7-mime"
    GURL url3 = test_server_.GetURL("/certs.p7c");
    std::unique_ptr<CertNetFetcher::Request> request3 = StartRequest(&fetcher, url3, callback3);

    // Wait for all of the requests to complete.
    std::unique_ptr<FetchResult> result1 = callback1.WaitForResult();
    std::unique_ptr<FetchResult> result2 = callback2.WaitForResult();
    std::unique_ptr<FetchResult> result3 = callback3.WaitForResult();

    // Verify the fetch results.
    result1->VerifySuccess("-cert.crt-\n");
    result2->VerifySuccess("-root.crl-\n");
    result3->VerifySuccess("-certs.p7c-\n");

    EXPECT_EQ(3, network_delegate_.created_requests());
}

// Fetch a caIssuers URL which has an unexpected extension and Content-Type.
// The extension is .txt and the Content-Type is text/plain. Despite being
// unusual this succeeds as the extension and Content-Type are not required to
// be meaningful.
TEST_F(CertNetFetcherImplTest, ContentTypeDoesntMatter)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    TestFetchCallback callback;
    GURL url = test_server_.GetURL("/foo.txt");
    std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
    std::unique_ptr<FetchResult> result = callback.WaitForResult();
    result->VerifySuccess("-foo.txt-\n");
}

// Fetch a URLs whose HTTP response code is not 200. These are considered
// failures.
TEST_F(CertNetFetcherImplTest, HttpStatusCode)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    // Response was HTTP status 404.
    {
        TestFetchCallback callback;
        GURL url = test_server_.GetURL("/404.html");
        std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
        std::unique_ptr<FetchResult> result = callback.WaitForResult();
        result->VerifyFailure(ERR_FAILED);
    }

    // Response was HTTP status 500.
    {
        TestFetchCallback callback;
        GURL url = test_server_.GetURL("/500.html");
        std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
        std::unique_ptr<FetchResult> result = callback.WaitForResult();
        result->VerifyFailure(ERR_FAILED);
    }
}

// Fetching a URL with a Content-Disposition header should have no effect.
TEST_F(CertNetFetcherImplTest, ContentDisposition)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    TestFetchCallback callback;
    GURL url = test_server_.GetURL("/downloadable.js");
    std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
    std::unique_ptr<FetchResult> result = callback.WaitForResult();
    result->VerifySuccess("-downloadable.js-\n");
}

// Verifies that a cachable request will be served from the HTTP cache the
// second time it is requested.
TEST_F(CertNetFetcherImplTest, Cache)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    // Fetch a URL whose HTTP headers make it cacheable for 1 hour.
    GURL url(test_server_.GetURL("/cacheable_1hr.crt"));
    {
        TestFetchCallback callback;

        std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
        std::unique_ptr<FetchResult> result = callback.WaitForResult();
        result->VerifySuccess("-cacheable_1hr.crt-\n");
    }

    EXPECT_EQ(1, network_delegate_.created_requests());

    // Kill the HTTP server.
    ASSERT_TRUE(test_server_.ShutdownAndWaitUntilComplete());

    // Fetch again -- will fail unless served from cache.
    {
        TestFetchCallback callback;
        std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
        std::unique_ptr<FetchResult> result = callback.WaitForResult();
        result->VerifySuccess("-cacheable_1hr.crt-\n");
    }

    EXPECT_EQ(2, network_delegate_.created_requests());
}

// Verify that the maximum response body constraints are enforced by fetching a
// resource that is larger than the limit.
TEST_F(CertNetFetcherImplTest, TooLarge)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    // This file has a response body 12 bytes long. So setting the maximum to 11
    // bytes will cause it to fail.
    GURL url(test_server_.GetURL("/certs.p7c"));
    TestFetchCallback callback;
    std::unique_ptr<CertNetFetcher::Request> request = fetcher.FetchCaIssuers(
        url, CertNetFetcher::DEFAULT, 11, callback.callback());

    std::unique_ptr<FetchResult> result = callback.WaitForResult();
    result->VerifyFailure(ERR_FILE_TOO_BIG);
}

// Set the timeout to 10 milliseconds, and try fetching a URL that takes 5
// seconds to complete. It should fail due to a timeout.
TEST_F(CertNetFetcherImplTest, Hang)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url(test_server_.GetURL("/slow/certs.p7c?5"));
    TestFetchCallback callback;
    std::unique_ptr<CertNetFetcher::Request> request = fetcher.FetchCaIssuers(
        url, 10, CertNetFetcher::DEFAULT, callback.callback());
    std::unique_ptr<FetchResult> result = callback.WaitForResult();
    result->VerifyFailure(ERR_TIMED_OUT);
}

// Verify that if a response is gzip-encoded it gets inflated before being
// returned to the caller.
TEST_F(CertNetFetcherImplTest, Gzip)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url(test_server_.GetURL("/gzipped_crl"));
    TestFetchCallback callback;
    std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
    std::unique_ptr<FetchResult> result = callback.WaitForResult();
    result->VerifySuccess("-gzipped_crl-\n");
}

// Try fetching an unsupported URL scheme (https).
TEST_F(CertNetFetcherImplTest, HttpsNotAllowed)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url("https://foopy/foo.crt");
    TestFetchCallback callback;
    std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
    // Should NOT complete synchronously despite being a test that could be done
    // immediately.
    EXPECT_FALSE(callback.HasResult());
    std::unique_ptr<FetchResult> result = callback.WaitForResult();
    result->VerifyFailure(ERR_DISALLOWED_URL_SCHEME);

    // No request was created because the URL scheme was unsupported.
    EXPECT_EQ(0, network_delegate_.created_requests());
}

// Try fetching a URL which redirects to https.
TEST_F(CertNetFetcherImplTest, RedirectToHttpsNotAllowed)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url(test_server_.GetURL("/redirect_https"));
    TestFetchCallback callback;

    std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);
    std::unique_ptr<FetchResult> result = callback.WaitForResult();
    result->VerifyFailure(ERR_DISALLOWED_URL_SCHEME);

    EXPECT_EQ(1, network_delegate_.created_requests());
}

// Try fetching an unsupported URL scheme (https) and then immediately
// cancelling. This is a bit special because this codepath needs to post a task.
TEST_F(CertNetFetcherImplTest, CancelHttpsNotAllowed)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url("https://foopy/foo.crt");
    TestFetchCallback callback;
    std::unique_ptr<CertNetFetcher::Request> request = StartRequest(&fetcher, url, callback);

    // Cancel the request.
    request.reset();

    // Spin the message loop to increase chance of catching a bug.
    base::RunLoop().RunUntilIdle();

    // Should NOT complete synchronously despite being a test that could be done
    // immediately.
    EXPECT_FALSE(callback.HasResult());

    EXPECT_EQ(0, network_delegate_.created_requests());
}

// Start a few requests, and cancel one of them before running the message loop
// again.
TEST_F(CertNetFetcherImplTest, CancelBeforeRunningMessageLoop)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);
    TestFetchCallback callback1;
    TestFetchCallback callback2;
    TestFetchCallback callback3;

    GURL url1 = test_server_.GetURL("/cert.crt");
    std::unique_ptr<CertNetFetcher::Request> request1 = StartRequest(&fetcher, url1, callback1);

    GURL url2 = test_server_.GetURL("/root.crl");
    std::unique_ptr<CertNetFetcher::Request> request2 = StartRequest(&fetcher, url2, callback2);

    GURL url3 = test_server_.GetURL("/certs.p7c");

    std::unique_ptr<CertNetFetcher::Request> request3 = StartRequest(&fetcher, url3, callback3);

    EXPECT_EQ(3, network_delegate_.created_requests());
    EXPECT_FALSE(callback1.HasResult());
    EXPECT_FALSE(callback2.HasResult());
    EXPECT_FALSE(callback3.HasResult());

    // Cancel the second request.
    request2.reset();

    // Wait for the non-cancelled requests to complete.
    std::unique_ptr<FetchResult> result1 = callback1.WaitForResult();
    std::unique_ptr<FetchResult> result3 = callback3.WaitForResult();

    // Verify the fetch results.
    result1->VerifySuccess("-cert.crt-\n");
    result3->VerifySuccess("-certs.p7c-\n");

    EXPECT_FALSE(callback2.HasResult());
}

// Start several requests, and cancel one of them after the first has completed.
// NOTE: The python test server is single threaded and can only service one
// request at a time. After a socket is opened by the server it waits for it to
// be completed, and any subsequent request will hang until the first socket is
// closed.
// Cancelling the first request can therefore be problematic, since if
// cancellation is done after the socket is opened but before reading/writing,
// then the socket is re-cycled and things will be stalled until the cleanup
// timer (10 seconds) closes it.
// To work around this, the last request is cancelled, and hope that the
// requests are given opened sockets in a FIFO order.
// TODO(eroman): Make this more robust.
TEST_F(CertNetFetcherImplTest, CancelAfterRunningMessageLoop)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);
    TestFetchCallback callback1;
    TestFetchCallback callback2;
    TestFetchCallback callback3;

    GURL url1 = test_server_.GetURL("/cert.crt");

    std::unique_ptr<CertNetFetcher::Request> request1 = StartRequest(&fetcher, url1, callback1);

    GURL url2 = test_server_.GetURL("/certs.p7c");
    std::unique_ptr<CertNetFetcher::Request> request2 = StartRequest(&fetcher, url2, callback2);

    GURL url3("ftp://www.not.supported.com/foo");
    std::unique_ptr<CertNetFetcher::Request> request3 = StartRequest(&fetcher, url3, callback3);

    EXPECT_FALSE(callback1.HasResult());
    EXPECT_FALSE(callback2.HasResult());
    EXPECT_FALSE(callback3.HasResult());

    // Wait for the ftp request to complete (it should complete right away since
    // it doesn't even try to connect to the server).
    std::unique_ptr<FetchResult> result3 = callback3.WaitForResult();
    result3->VerifyFailure(ERR_DISALLOWED_URL_SCHEME);

    // Cancel the second outstanding request.
    request2.reset();

    // Wait for the first request to complete.
    std::unique_ptr<FetchResult> result2 = callback1.WaitForResult();

    // Verify the fetch results.
    result2->VerifySuccess("-cert.crt-\n");
}

// Delete a CertNetFetcherImpl with outstanding requests on it.
TEST_F(CertNetFetcherImplTest, DeleteCancels)
{
    ASSERT_TRUE(test_server_.Start());

    std::unique_ptr<CertNetFetcherImpl> fetcher(
        new CertNetFetcherImpl(&context_));

    GURL url(test_server_.GetURL("/slow/certs.p7c?20"));
    TestFetchCallback callback;
    std::unique_ptr<CertNetFetcher::Request> request = StartRequest(fetcher.get(), url, callback);

    // Destroy the fetcher before the outstanding request.
    fetcher.reset();
}

// Fetch the same URLs in parallel and verify that only 1 request is made per
// URL.
TEST_F(CertNetFetcherImplTest, ParallelFetchDuplicates)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url1 = test_server_.GetURL("/cert.crt");
    GURL url2 = test_server_.GetURL("/root.crl");

    // Issue 3 requests for url1, and 3 requests for url2
    TestFetchCallback callback1;
    std::unique_ptr<CertNetFetcher::Request> request1 = StartRequest(&fetcher, url1, callback1);

    TestFetchCallback callback2;
    std::unique_ptr<CertNetFetcher::Request> request2 = StartRequest(&fetcher, url2, callback2);

    TestFetchCallback callback3;
    std::unique_ptr<CertNetFetcher::Request> request3 = StartRequest(&fetcher, url1, callback3);

    TestFetchCallback callback4;
    std::unique_ptr<CertNetFetcher::Request> request4 = StartRequest(&fetcher, url2, callback4);

    TestFetchCallback callback5;
    std::unique_ptr<CertNetFetcher::Request> request5 = StartRequest(&fetcher, url2, callback5);

    TestFetchCallback callback6;
    std::unique_ptr<CertNetFetcher::Request> request6 = StartRequest(&fetcher, url1, callback6);

    // Cancel all but one of the requests for url1.
    request1.reset();
    request3.reset();

    // Wait for the remaining requests to finish.
    std::unique_ptr<FetchResult> result2 = callback2.WaitForResult();
    std::unique_ptr<FetchResult> result4 = callback4.WaitForResult();
    std::unique_ptr<FetchResult> result5 = callback5.WaitForResult();
    std::unique_ptr<FetchResult> result6 = callback6.WaitForResult();

    // Verify that none of the cancelled requests for url1 completed (since they
    // were cancelled).
    EXPECT_FALSE(callback1.HasResult());
    EXPECT_FALSE(callback3.HasResult());

    // Verify the fetch results.
    result2->VerifySuccess("-root.crl-\n");
    result4->VerifySuccess("-root.crl-\n");
    result5->VerifySuccess("-root.crl-\n");
    result6->VerifySuccess("-cert.crt-\n");

    // Verify that only 2 URLRequests were started even though 6 requests were
    // issued.
    EXPECT_EQ(2, network_delegate_.created_requests());
}

// Cancel a request and then start another one for the same URL.
TEST_F(CertNetFetcherImplTest, CancelThenStart)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);
    TestFetchCallback callback1;
    TestFetchCallback callback2;
    TestFetchCallback callback3;

    GURL url = test_server_.GetURL("/cert.crt");

    std::unique_ptr<CertNetFetcher::Request> request1 = StartRequest(&fetcher, url, callback1);
    request1.reset();

    std::unique_ptr<CertNetFetcher::Request> request2 = StartRequest(&fetcher, url, callback2);

    std::unique_ptr<CertNetFetcher::Request> request3 = StartRequest(&fetcher, url, callback3);
    request3.reset();

    // All but |request2| were canceled.
    std::unique_ptr<FetchResult> result = callback2.WaitForResult();

    result->VerifySuccess("-cert.crt-\n");

    EXPECT_FALSE(callback1.HasResult());
    EXPECT_FALSE(callback3.HasResult());

    // One URLRequest that was cancelled, then another right afterwards.
    EXPECT_EQ(2, network_delegate_.created_requests());
}

// Start duplicate requests and then cancel all of them.
TEST_F(CertNetFetcherImplTest, CancelAll)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);
    TestFetchCallback callback[3];
    std::unique_ptr<CertNetFetcher::Request> request[3];

    GURL url = test_server_.GetURL("/cert.crt");

    for (size_t i = 0; i < arraysize(callback); ++i) {
        request[i] = StartRequest(&fetcher, url, callback[i]);
    }

    // Cancel all the requests.
    for (size_t i = 0; i < arraysize(request); ++i)
        request[i].reset();

    EXPECT_EQ(1, network_delegate_.created_requests());

    for (size_t i = 0; i < arraysize(request); ++i)
        EXPECT_FALSE(callback[i].HasResult());
}

void DeleteCertNetFetcher(CertNetFetcher* fetcher)
{
    delete fetcher;
}

// Delete the CertNetFetcherImpl within a request callback.
TEST_F(CertNetFetcherImplTest, DeleteWithinCallback)
{
    ASSERT_TRUE(test_server_.Start());

    // Deleted by callback2.
    CertNetFetcher* fetcher = new CertNetFetcherImpl(&context_);

    GURL url = test_server_.GetURL("/cert.crt");

    TestFetchCallback callback[4];
    std::unique_ptr<CertNetFetcher::Request> reqs[4];
    callback[1].set_extra_closure(base::Bind(DeleteCertNetFetcher, fetcher));

    for (size_t i = 0; i < arraysize(callback); ++i)
        reqs[i] = StartRequest(fetcher, url, callback[i]);

    EXPECT_EQ(1, network_delegate_.created_requests());

    callback[1].WaitForResult();

    // Assume requests for the same URL are executed in FIFO order.
    EXPECT_TRUE(callback[0].HasResult());
    EXPECT_FALSE(callback[2].HasResult());
    EXPECT_FALSE(callback[3].HasResult());
}

void FetchRequest(CertNetFetcher* fetcher,
    const GURL& url,
    TestFetchCallback* callback,
    std::unique_ptr<CertNetFetcher::Request>* request)
{
    *request = StartRequest(fetcher, url, *callback);
}

// Make a request during callback for the same URL.
TEST_F(CertNetFetcherImplTest, FetchWithinCallback)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url = test_server_.GetURL("/cert.crt");

    TestFetchCallback callback[5];
    std::unique_ptr<CertNetFetcher::Request> req[5];
    callback[1].set_extra_closure(
        base::Bind(FetchRequest, &fetcher, url, &callback[4], &req[4]));

    for (size_t i = 0; i < arraysize(callback) - 1; ++i)
        req[i] = StartRequest(&fetcher, url, callback[i]);

    EXPECT_EQ(1, network_delegate_.created_requests());

    for (size_t i = 0; i < arraysize(callback); ++i) {
        std::unique_ptr<FetchResult> result = callback[i].WaitForResult();
        result->VerifySuccess("-cert.crt-\n");
    }

    // The fetch started within a callback should have started a new request
    // rather than attaching to the current job.
    EXPECT_EQ(2, network_delegate_.created_requests());
}

void CancelRequest(std::unique_ptr<CertNetFetcher::Request>* request)
{
    request->reset();
}

// Cancel a request while executing a callback for the same job.
TEST_F(CertNetFetcherImplTest, CancelWithinCallback)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url = test_server_.GetURL("/cert.crt");

    TestFetchCallback callback[4];
    std::unique_ptr<CertNetFetcher::Request> request[4];

    for (size_t i = 0; i < arraysize(callback); ++i)
        request[i] = StartRequest(&fetcher, url, callback[i]);

    // Cancel request[2] when the callback for request[1] runs.
    callback[1].set_extra_closure(base::Bind(CancelRequest, &request[2]));

    EXPECT_EQ(1, network_delegate_.created_requests());

    for (size_t i = 0; i < arraysize(request); ++i) {
        if (i == 2)
            continue;

        std::unique_ptr<FetchResult> result = callback[i].WaitForResult();
        result->VerifySuccess("-cert.crt-\n");
    }

    // request[2] was cancelled.
    EXPECT_FALSE(callback[2].HasResult());
}

// Cancel the final request while executing a callback for the same job. Ensure
// that the job is not deleted twice.
TEST_F(CertNetFetcherImplTest, CancelLastRequestWithinCallback)
{
    ASSERT_TRUE(test_server_.Start());

    CertNetFetcherImpl fetcher(&context_);

    GURL url = test_server_.GetURL("/cert.crt");

    TestFetchCallback callback1;
    std::unique_ptr<CertNetFetcher::Request> request1 = StartRequest(&fetcher, url, callback1);

    TestFetchCallback callback2;
    std::unique_ptr<CertNetFetcher::Request> request2 = StartRequest(&fetcher, url, callback1);

    // Cancel request2 when the callback for request1 runs.
    callback1.set_extra_closure(base::Bind(CancelRequest, &request2));

    EXPECT_EQ(1, network_delegate_.created_requests());

    std::unique_ptr<FetchResult> result = callback1.WaitForResult();
    result->VerifySuccess("-cert.crt-\n");

    // request2 was cancelled.
    EXPECT_FALSE(callback2.HasResult());
}

} // namespace net
